﻿using HotChocolate.Execution;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using Microsoft.Extensions.Options;
using Volo.Abp.Uow;

namespace AbpIntegrationDemo.Api.Infrastructures.GraphQLs;

internal sealed class UnitOfWorkMiddleware
{
    private readonly HotChocolate.Execution.RequestDelegate _next;

    public UnitOfWorkMiddleware(HotChocolate.Execution.RequestDelegate next)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async ValueTask InvokeAsync(IRequestContext context)
    {
        if (context.Operation is null)
        {
            await _next(context).ConfigureAwait(false);
            return;
        }

        var serviceScopeFactory = context.Services.GetService<IServiceScopeFactory>();
        using (var scope = serviceScopeFactory!.CreateScope())
        {
            var options = CreateOptions(scope.ServiceProvider, context.Operation);
            var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
            if (unitOfWorkManager.TryBeginReserved(UnitOfWork.UnitOfWorkReservationName, options))
            {
                await _next(context).ConfigureAwait(false);

                if (context.Result is QueryResult { Errors: { Count: 0 } } || context.Result is QueryResult { Errors: null })
                {
                    if (unitOfWorkManager.Current != null)
                    {
                        await unitOfWorkManager.Current.SaveChangesAsync();
                    }
                }
                else
                {
                    if (unitOfWorkManager.Current != null)
                    {
                        await unitOfWorkManager.Current.RollbackAsync();
                    }
                }

                return;
            }

            using (var uow = unitOfWorkManager.Begin(options))
            {
                await _next(context).ConfigureAwait(false);

                if (context.Result is QueryResult { Errors: { Count: 0 } } || context.Result is QueryResult { Errors: null })
                {
                    await uow.CompleteAsync();
                }
                else
                {
                    await uow.RollbackAsync();
                }
            }
        }
    }

    private AbpUnitOfWorkOptions CreateOptions(IServiceProvider serviceProvider, IOperation operation)
    {
        var options = new AbpUnitOfWorkOptions();
        var defaultOptions = serviceProvider.GetRequiredService<IOptions<AbpUnitOfWorkDefaultOptions>>().Value;
        options.IsTransactional = defaultOptions.CalculateIsTransactional(autoValue: operation.Definition.Operation is OperationType.Mutation);
        return options;
    }
}